
package com.eva.framework.dbpool;

import com.eva.epc.common.util.CommonUtils;
import com.eva.epc.common.util.EException;
import com.eva.framework.utils.LoggerFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.slf4j.Logger;

import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

/**
 * 本对象是作为绑定对应c3p0连接池的实用类，使用时先实例化之（如果不需指定池对象则默认会绑定到默认的c3p0池）.
 *
 * <pre>
 * 1)freeConnection方法相当于调用ComboPooledDataSource继承自自PooledDataSource的close方法（
 * 		调用本方法即是把连接放回连接池而非真正物理关闭）；
 * 2)realese方法用于重启绑定的连接池对象；
 * 3)close关闭当前DB对关联的c3p0池实例（如果池存在的话），这个关闭实际上是调用c3p0继承自PooledDataSource类
 * 的close方法，它将释放c3p0池所对应的所有资源，关闭后该池将无法使用。
 * </pre>
 * <p>
 * 另注：娄库类型设定见字段DB_TYPE，本字段是全局标识，后绪的相关方法会据此判断
 * 得出DB类型并对一些数据库相关的SQL作相应转换.
 * </p>
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @see com.eva.framework.dbpool.DBUtils
 * @see com.mchange.v2.c3p0.ComboPooledDataSource
 */
public class DBShell {

    private static final Logger logger = LoggerFactory.getLog();

    /**
     * 数据库类型.<br>
     * 本字段是全局标识，后绪的相关方法会据此判断得出DB类型并对一些数据库相关的SQL作相应转换
     */
    public static DBType DB_TYPE = DBType.sqlServer;

    /**
     * 提取实体表数据行时，对于NULL值列数据的替换值。
     * 当需要判断字段值是否对应数据库定义的NULL时，可以
     * 简单通过相等比较运算符来判断。
     */
    public static final String NULL_OF_RESULTSET = "";
    /**
     * 日期格式"yyyy-MM-dd"，用于Java程序代码中的格式转换（不用于SQl中，因不同的数据库pattern要求是不一样的）
     */
    public static final SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
    /**
     * 到分的日期格式"yyyy-MM-dd HH:mm"，用于Java程序代码中的格式转换（不用于SQl中，因不同的数据库pattern要求是不一样的）
     */
    public static final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    /**
     * SQLException异常message总是包含数据库管理系统的前缀标志，如"ORA-00001: "、"[Microsoft][OD
     * BC SQL Server Driver][SQL Server]"等，为了友好显示给最终用户，剔除这些没有任何实际意义的标
     * 志内容而定义了本变量，用于设定原始错误消息起始多少个字符可以剔除。默认为0，表不剔除这个标识头。
     * 本方法目前仅用于方法 {@link DBUtils##getErrorMessage(SQLException)}的表现，详情请参见该方法.
     */
    public static int dbExceptionIdentStringLen = 0;
    /**
     * 在出错时是否需要解析更详细的异常信息，本字段目前仅用来控制方
     * 法 {@link DBUtils#getErrorMessage(SQLException)}的表现，详情请参见该方法.
     */
    public static boolean needParseNextExceptionsForSQLException = true;
    public static final IllegalArgumentException UPDATE_DATA_ILEGEL_EXCEPTION =
            new IllegalArgumentException("当前更新数据无效，原因可能是数据在你本次操作之前已经被改动，请确认后再试！");

    /**
     * 本列表用于存放所有已实例化的c3p0池对象（key值与ComboPooledDataSource对象本身属性无关，是DB对象内约定的
     * ，仅用于本链表的key）
     */
    private final static HashMap<String, ComboPooledDataSource> c3p0PooledDataSourceList = new HashMap();
    /**
     * 默认的c3p0连接池存放于c3p0PooledDataSourceList链表的名称（与其对象本身无关），相应地方如
     * 不指明池名称时会默认使用它
     */
    private final static String DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME = "_default_c3p0_ds_";

    /**
     * 处理prepare statement 中的字符串占位符时，使用的SQL 数据类型。默认为java.sql.Types.VARCHAR
     */
    private int sqlTypeForString = Types.VARCHAR;
    /**
     * 日期类型字段对应的SQL数据类型。默认为java.sql.Types.TIMESTAMP
     */
    private int sqlTypeForDate = Types.TIMESTAMP;
    /**
     * 本DB对象对应的c3p0连接池实例（一旦绑定到对应的池实例，则本DB对象中的所有有关数据库操作就都是基于它的）
     */
    private ComboPooledDataSource currentC3p0PoolInstance;
    /**
     * 当前DB对象对应的c3p0连接池实例名（该名称是DB中约定好的，用于c3p0PooledDataSourceList链进行存放管理，
     * 与ComboPooledDataSource对象本身的名称属性无关）
     */
    private String currentC3p0PoolInstanceName;

    /**
     * 默认构管中造方法.
     * <pre>
     * 	本构造器首先会在c3p0PooledDataSourceList链表中查找默认名称为DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME
     * 	的实例，如果找到了则把本DB对象跟它绑定，否则新建一个默认的c3p0池对象放入链表并与本DB对象绑定（注该默认
     * 	c3p0池对象实例化时按官方文档：它将自动搜索配置文件c3p0.properties并读取它的配置内容（本平台下它默认在程序根目录）.
     * </pre>
     */
    public DBShell() {
        //首先在池对象链表中查找默认的c3p0实例
        ComboPooledDataSource defaultPool = c3p0PooledDataSourceList.get(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME);
        if (defaultPool == null)
            //如果默认实例不存在则实例化一个并放入链表中
            defaultPool = putAPoolInstance(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME, new ComboPooledDataSource());
        //将本DB对象与默认的池对象绑定在一起（以后DB对象的所有操作就是依靠 该c3p0实例完成的）
        bindToPoolInstance(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME, defaultPool);
    }

    /**
     * 本构造方法表示把一个指定的c3p0实例作为默认池实例放入链表（如果默认实例不存在）并与本DB对象绑定.
     *
     * @param c3p0PooledDataSource 要作为默认c3p0池实例的对象
     */
    public DBShell(ComboPooledDataSource c3p0PooledDataSource) {
        this(DEFAULT_C3P0_POOLED_DATA_SOURCE_NAME, c3p0PooledDataSource);
    }

    /**
     * 将指定的c3p0池对象以指定的名称放入链表，并与本DB对象进行绑定（如果名称已经存
     * 在于链表中则本对象会实例化失败）.
     *
     * @param poolName             指定要放入表中的池名称（与ComboPooledDataSource对象本身的名称属性无关，仅对
     *                             c3p0PooledDataSourceList有意义
     * @param c3p0PooledDataSource 池实例
     */
    public DBShell(String poolName, ComboPooledDataSource c3p0PooledDataSource) {
        this(poolName, c3p0PooledDataSource, false);
    }

    /**
     * 将指定的c3p0池对象以指定的名称放入链表，并与本DB对象进行绑定.
     *
     * @param poolName             指定要放入表中的池名称（与ComboPooledDataSource对象本身的名称属性无关，仅对
     *                             c3p0PooledDataSourceList有意义
     * @param c3p0PooledDataSource 池实例
     * @param coverIfExist         false表示如果名称存在则不无放入链表，实例化DB会失败，否则直接覆盖原先的同名实例
     */
    public DBShell(String poolName, ComboPooledDataSource c3p0PooledDataSource, boolean coverIfExist) {
        //把实例放入链表存放起来
        putAPoolInstance(poolName, c3p0PooledDataSource, coverIfExist);
        //把当前的DB对象与之绑定
        bindToPoolInstance(poolName, c3p0PooledDataSource);
    }

    /**
     * 将c3p0实例已指定的名称绑定到当前DB对象，一旦绑定到对应的池实例，
     * 则本DB对象中的所有有关数据库操作就都是基于它的.
     *
     * @param poolName             用于标识当前DB使用的池实例的名称（与ComboPooledDataSource本身的名称属性无关，仅针对链表
     *                             c3p0PooledDataSourceList有意义）
     * @param c3p0PooledDataSource 池实例
     */
    public void bindToPoolInstance(String poolName, ComboPooledDataSource c3p0PooledDataSource) {
        if (CommonUtils.isStringEmpty(poolName, true) || c3p0PooledDataSource == null)
            throw new IllegalArgumentException("参数无效,poolName="
                    + poolName + ",c3p0PooledDataSource=" + c3p0PooledDataSource);
        this.currentC3p0PoolInstance = c3p0PooledDataSource;
        this.currentC3p0PoolInstanceName = poolName;
    }

    /**
     * 本方法的作用仅是将一池实例以指定的名称放入c3p0PooledDataSourceList链表（如果指定名称存在则本次调用会失败）.
     *
     * @param poolName             用于标识当前DB使用的池实例的名称，与ComboPooledDataSource本身的名称属性无关，仅针对链表
     *                             c3p0PooledDataSourceList有意义
     * @param c3p0PooledDataSource 池实例
     * @return 调用成功后返回原c3p0PooledDataSource对象本身，无特殊作用
     */
    public static ComboPooledDataSource putAPoolInstance(String poolName, ComboPooledDataSource c3p0PooledDataSource) {
        return putAPoolInstance(poolName, c3p0PooledDataSource, false);
    }

    /**
     * 本方法的作用仅是将一池实例以指定的名称放入c3p0PooledDataSourceList链表.
     *
     * @param poolName             用于标识当前DB使用的池实例的名称，与ComboPooledDataSource本身的名称属性无关，仅针对链表
     *                             c3p0PooledDataSourceList有意义
     * @param c3p0PooledDataSource 池实例
     * @param coverIfExist         false表示如果名称存在则不无放入链表，实例化DB会失败，否则直接覆盖原先的同名实例
     * @return 调用成功后返回原c3p0PooledDataSource对象本身，无特殊作用
     */
    public static ComboPooledDataSource putAPoolInstance(String poolName
            , ComboPooledDataSource c3p0PooledDataSource, boolean coverIfExist) {
        if (CommonUtils.isStringEmpty(poolName, true))
            throw new IllegalArgumentException("连接池名称不可为空，新连接池实例不允许加入到连接池链.");
        if (coverIfExist && c3p0PooledDataSourceList.containsKey(poolName))
            throw new IllegalArgumentException("连接池" + poolName + "已经存在，不允许覆盖原先的实例.");
        else {
            c3p0PooledDataSourceList.put(poolName, c3p0PooledDataSource);
            return c3p0PooledDataSource;
        }
    }

    /**
     * 获取当前DB对象关联的连接池名称
     *
     * @return String 连接池名，与ComboPooledDataSource本身的名称属性无关，仅针对链表
     * c3p0PooledDataSourceList有意义
     */
    public String getBindPoolName() {
        return this.currentC3p0PoolInstanceName;
    }

    /**
     * <p>
     * <b>预编译语句查询，并返回查询结果</b>.<br>
     * 获得给定查询语句的数据（使用预编译语句的方式，形如：select f1,f2,f3 from t where key=? and oth=?），
     * 并以Vector返回。<br><br>
     *
     * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
     * 在结果集为空的情况，也会保证返回的是空的2D向量实例），详情参见方法 {@link #parseToVector(ResultSet)} .
     * </p>
     *
     * @param sql         预编译查询语句
     * @param paramValues 预编译语句中对应参数的值(每一项都不能为null)
     * @return 以2D向量组织查询结果
     */
    public Vector<Vector<?>> queryData(String sql, Object[] paramValues) throws Exception {
        if (CommonUtils.isStringEmpty(sql, true))
            throw new IllegalArgumentException("sql语句是空的，参数无效！");

        //声明返回数据容器变量
        Vector<Vector<?>> returnValue = null;
        Connection con = getConnection();
        PreparedStatement pstmt = null;
        try {
            LoggerFactory.getLog().debug("queryData{SQL: ".concat(sql)
                    .concat(" , VALUES: ").concat(Arrays.toString(paramValues).concat("}")));
            pstmt = con.prepareStatement(sql);
            this.setParameters(pstmt, paramValues);
            returnValue = parseToVector(pstmt.executeQuery());
        } catch (SQLException sqle) {
            LoggerFactory.getLog().warn("Exception thrown when execute [" + sql + ']');
            throw DBUtils.getErrorMessage(sqle);
        } finally {
            try {
                if (pstmt != null)
                    pstmt.close();
            } catch (Throwable e) {
            }
            this.closeConnection(con);
        }
        returnValue.trimToSize();

        return returnValue;
    }

    /**
     * <p>
     * <b>普通SQL语句执行查询，并返回查询结果</b>.<br>
     * 获得给定查询语句的数据（不是使用预编译语句，形如：select f1,f2,f3 from t where key='abc' and oth='cdf'）
     * ，并以Vector返回。<br><br>
     *
     * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
     * 在结果集为空的情况，也会保证返回的是空的2D向量实例），详情参见方法 {@link #parseToVector(ResultSet)} .
     * </p>
     *
     * @param sql 查询语句
     * @return 以2D向量组织查询结果
     */
    public Vector<Vector<?>> queryData(String sql) throws Exception {
        //声明返回数据容器变量
        Vector<Vector<?>> returnValue = null;
        if (CommonUtils.isStringEmpty(sql, true))
            throw new IllegalArgumentException("sql语句是空的，参数无效！");
        Connection con = getConnection();
        Statement st = null;
        try {
            LoggerFactory.getLog().debug("queryData{SQL: ".concat(sql).concat("}"));
            st = con.createStatement();
            returnValue = parseToVector(st.executeQuery(sql));
        } catch (SQLException sqle) {
            LoggerFactory.getLog().warn("异常发生在执行语句  [" + sql + ']');
            throw DBUtils.getErrorMessage(sqle);
        } finally {
            try {
                if (st != null)
                    st.close();
            } catch (Throwable e) {
                logger.error(e.getMessage(), e);
            }
            this.closeConnection(con);
        }
        return returnValue;
    }

    /**
     * <p>
     * 解析数据库查询结果集以便织成2D向量并返回之。<br>
     *
     * <b>提示：</b>本方法在正常执行的情况下将保证无条件返回一个2D向量实例（即使
     * 在结果集为空的情况，也会保证返回的是空的2D向量实例）.
     * </p>
     *
     * @param resultSet 要解析的目标结果集
     * @return 返回解析并组织完成的2D向量
     */
    private Vector<Vector<?>> parseToVector(ResultSet resultSet) throws SQLException {
        //结果集元数据
        ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
        //结果集总列数
        int columnCount = resultSetMetaData.getColumnCount();
        //该字段是否是时间戳类型
        boolean[] isDatetime = new boolean[columnCount + 1];
        for (int i = 1; i <= columnCount; i++)
            isDatetime[i] = resultSetMetaData.getColumnType(i) == sqlTypeForDate;

        //用于存放解析得到的数据的2d向量
        Vector<Vector<?>> results = new Vector<>();
        //字段值（它可能是任意数据类型）
        Object fieldValue;
        while (resultSet.next()) {
            Vector<Object> row = new Vector<>(columnCount);
            //结果集的游标是从1作为起始索引的（不是0，详见JDBC API）
            for (int i = 1; i <= columnCount; i++) {
                if (isDatetime[i]) {
                    //额外处理日期类型
                    Timestamp st = resultSet.getTimestamp(i);
                    if (st != null) {
                        SimpleDateFormat format;
                        //假定秒及和毫秒部分对我们没有任何意义
                        if (st.getHours() == 0 && st.getMinutes() == 0)
                            format = DBShell.dayFormat;
                        else
                            format = DBShell.timeFormat;
                        fieldValue = format.format(st);
                    } else
                        fieldValue = null;
                }
                //这对于SQLserver2005来说这些字段里存放的就是比特数组
                else if (resultSetMetaData.getColumnType(i) == Types.LONGVARBINARY
                        || resultSetMetaData.getColumnType(i) == Types.VARBINARY
                        || resultSetMetaData.getColumnType(i) == Types.BINARY)
                    fieldValue = resultSet.getBytes(i);//这个字段是SQL Server 2005的varbinary或相似列
                else
                    fieldValue = resultSet.getString(i);// getObject不行吗？不行，比如整数返回的是BigDecimal，处理起来就麻烦了，如需特殊处理的列就在上一个esle if里加吧
//				row.add(fieldValue==null?NULL:fieldValue.trim());
                row.add(fieldValue == null ? NULL_OF_RESULTSET : (fieldValue instanceof String
                        ? ((String) fieldValue).trim() : fieldValue));
            }
            results.add(row);
        }
//		results.trimToSize();
        return results;
    }

    /**
     * 为预编译语句中的参数设置值
     *
     * @param paramValues 参数的实际值目前支持的java对象类型有
     *                    <ol>
     *                    <li>Boolean,Integer,Long,Float,Double,java.util.Date,java.util.Calendar,String,byte[]</li>
     *                    </ol>
     */
    private void setParameters(PreparedStatement pstmt, Object paramValues[])
            throws SQLException {
        if (paramValues != null) {
            Object curr;
            int j;
            for (int i = 0; i < paramValues.length; i++) {
                curr = paramValues[i];
                j = i + 1;
                if (curr == null || "".equals(curr))
                    pstmt.setNull(j, Types.VARCHAR);
                else if (curr instanceof String)
                    pstmt.setObject(j, curr, sqlTypeForString);
                else if (curr instanceof Integer)
                    pstmt.setInt(j, ((Integer) curr).intValue());
                else if (curr instanceof java.util.Date)
                    pstmt.setTimestamp(j, new java.sql.Timestamp(((java.util.Date) curr).getTime()));
                else if (curr instanceof java.util.Calendar)
                    pstmt.setTimestamp(j, new java.sql.Timestamp((((java.util.Calendar) curr).getTime()).getTime()));
                else if (curr instanceof Long)
                    pstmt.setLong(j, ((Long) curr).longValue());
                else
                    pstmt.setObject(j, curr);
            }
        }
    }

    /**
     * <p><b>单个预编译语句执行更新.</b></p>
     * 使用预编译方式执行关系表的更新，本方法适用于单条SQL语句的情况，省得新建一个Vector。
     *
     * @param sql            预编译更新语句
     * @param paramValues    预编译语句中对应参数的值。目前支持的java对象类型有
     *                       <ol>
     *                       <li>Boolean,Integer,Long,Float,Double,java.util.Date,java.util.Calendar,String,byte[]</li>
     *                       </ol>
     * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
     * @return 该语句的执行所影响关系表的数据行数目是否大于零
     */
    public boolean update(String sql, Object[] paramValues
            , boolean rollbackIfZero) throws Exception {
        Vector<Object> sqls = new Vector<>(1);
        if (sql != null)
            sqls.add(sql);
        Object values[][] = new Object[1][paramValues == null ? 0 : paramValues.length];
        for (int i = 0; i < values[0].length; i++)
            values[0][i] = paramValues[i];
        return this.update(sqls, values, rollbackIfZero);
    }

    /**
     * <p><b>多个预编译语句执行更新（该指语句实际是没有需要填充的参数的，即paramValues=空Vector，它们形如：
     * "update someTable where a='123' 或不是像这样 update someTable where a=?"，本方法主要是提供
     * 一个方便地批量执行这类语句的接口）.</b></p>
     * <p>
     * 使用预编译方式执行关系表的更新.
     *
     * @param sqls           预编译更新语句集
     * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
     * @return 该语句的执行所影响关系表的数据行数目是否大于零
     * @see #update(List, List, boolean)
     */
    public boolean update(List<?> sqls, boolean rollbackIfZero) throws Exception {
        return update(sqls, new Vector<>(), rollbackIfZero);
    }

    /**
     * <p><b>多个预编译语句执行更新.</b></p>
     * 使用预编译方式执行关系表的更新.
     *
     * @param sqls           预编译更新语句集
     * @param paramValues    每条预编译语句参数值数组构成的向量, 即<br>
     *                       <code>paramValues.get(i) instanceof Vector == true</code>，<br>
     *                       paramValues.get(i)[j] 就是对应prepareSql中第j个?的值。
     * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
     * @return 该语句的执行所影响关系表的数据行数目是否大于零
     * @see #update(List, Object[][], boolean)
     */
    public boolean update(List<?> sqls, List paramValues, boolean rollbackIfZero) throws Exception {
        return update(sqls
                , (Object[][]) paramValues.toArray(new Object[paramValues.size()][0])
                , rollbackIfZero);
    }

    /**
     * <p><b>多个预编译语句执行更新.</b>
     * 使用预编译方式执行关系表的更新
     * </p>
     *
     * @param sqls           预编译更新语句集
     * @param paramValues    预编译语句中对应参数的值
     * @param rollbackIfZero 如果有一条语句未影响关系表的数据行，确定该事务是否提交
     * @return 该语句的执行所影响关系表的数据行数目是否大于零
     * @throws IllegalArgumentException 如果rollbackIfZero==true且该SQL执行时影响行数是0则抛出本异常
     * @throws Exception                如果在执行SQL的任一环节中出现其它异常则抛出本异常
     */
    public boolean update(List<?> sqls, Object[][] paramValues, boolean rollbackIfZero) throws Exception {
        if (sqls == null)
            throw new IllegalArgumentException("sqls语句集合是空的，参数无效！");
        Connection con = getConnection();
        PreparedStatement pstmt = null;
        int i = 0;
        try {
            con.setAutoCommit(false);
            boolean effected = true;//判断更新语句是否影响了数据库表中的行
            for (int n = sqls.size(); i < n; i++) {
                pstmt = con.prepareStatement((String) sqls.get(i));
                //预编译语句的参数不为空才需要填充value（为空的表示该update语句里没有额外的?号即没有
                //额外需要填值的参数，直接执行即可）
                if (paramValues != null && paramValues.length > 0)
                    this.setParameters(pstmt, paramValues[i]);
                int cnt = pstmt.executeUpdate();
                if (cnt <= 0) {
                    LoggerFactory.getLog().warn("语句  [" + sqls.get(i) + "]执行时，影响行数为0!<rollbackIfZero?" + rollbackIfZero + ">");
                    effected = false;
                    if (rollbackIfZero)
                        break;
                }
                pstmt.close();
            }
            if (!effected && rollbackIfZero) {
                con.rollback();
                throw UPDATE_DATA_ILEGEL_EXCEPTION;
            } else
                con.commit();
            return effected;
        } catch (SQLException ex) {
            String valuesForLog = null;
            if (paramValues != null && paramValues.length > 0) {
                // 为了更强壮的代码：防止可能的数组越界
                try {
                    valuesForLog = " , VALUES: [" + (Arrays.toString(paramValues[i])) + "]";
                } catch (Exception e) {
                    LoggerFactory.getLog().warn("异常发生在valuesForLog!", e);
                }
            } else
                valuesForLog = "";
            LoggerFactory.getLog().warn("异常发生在执行SQL: [" + sqls.get(i) + "]" + valuesForLog);

            EException err = DBUtils.getErrorMessage(ex);
            EException retErr = err;
            try {
                con.rollback();
            } catch (SQLException se) {
                EException err2 = DBUtils.getErrorMessage(se);
                retErr = new EException(err.getShortMessage().concat("\r\n").concat(err2.getShortMessage())
                        , err.getMessage().concat("\r\n").concat(err2.getMessage()), err.getCause());
            }
            throw retErr;
        } finally {
            try {
                if (pstmt != null)
                    pstmt.close();
                con.setAutoCommit(true);
            } catch (Throwable t) {
                LoggerFactory.getLog().warn(t.getMessage(), t);
            } finally {
                this.closeConnection(con);
            }
        }
    }

    /**
     * 按默认连接名获取连接
     */
    public Connection getConnection() throws Exception {
        Connection conn = currentC3p0PoolInstance.getConnection();//this.getPoolName());
        if (conn == null)
            throw new IllegalStateException("在本DB对象中，不能获得关联的池 "
                    + this.getBindPoolName() + " 中的连接，获取数据库连接失败！");
        checkConnection(conn);
        return conn;
    }

    /**
     * 按默认连接名释放连接（实际上是把物理连接对象放入池中，由c3p0连接池来决定如何处理它）.
     */
    public void closeConnection(Connection con) throws Exception {
        if (con != null) {
            checkConnection(con);
//				pm.freeConnection(this.getPoolName(),con);
            con.close();//如果是关闭从连接池中获得的连接，则此关闭并没有从物理上关闭，仅放回了连接池而已
        }
    }

    /**
     * 关闭当前DB对关联的c3p0池实例（如果池存在的话），这个关闭实际上是调用c3p0继承自PooledDataSource类
     * 的close方法，它将释放c3p0池所对应的所有资源，关闭后该池将无法使用
     */
    public void close() {
        if (currentC3p0PoolInstance != null)
            //关闭所有跟c3p0相关的东西
            currentC3p0PoolInstance.close();
    }

    /**
     * 本方法的作用相当于重启当前DB对象关联的currentC3p0PoolInstance实例，默认是软重启，
     * 即仅关闭所有的checkinconnection,重新初始化池.
     */
    public void release() throws Exception {
        release(false);
    }

    /**
     * 本方法的作用相当于重启当前DB对象关联的currentC3p0PoolInstance实例.
     *
     * @param hardRelease true 表示数据源硬重启（包括数据源对象本身也全部是新对象）
     *                    ，否则是数据源软重启（即仅关闭所有的checkinconnection,重新初始化池）
     */
    public void release(boolean hardRelease) throws Exception {
//		this.pm.release();
        if (hardRelease)
            //数据源硬重启（包括数据源本身全部是新的）
            currentC3p0PoolInstance.hardReset();
        else
            //数据源软重启（关闭所有checkinconnection,重新初始化池）
            currentC3p0PoolInstance.softReset(currentC3p0PoolInstance.getUser(), currentC3p0PoolInstance.getPassword());
    }

    /**
     * 检测该连接是否为事务自动提交方式，如不是则自动设为true。
     *
     * @see Connection#setAutoCommit(boolean)
     */
    private void checkConnection(Connection connection) {
        try {
            if (!connection.getAutoCommit())
                connection.setAutoCommit(true);
        } catch (SQLException ex) {
            System.out.println(ex.getMessage());
        }
    }

    public int getSqlTypeForString() {
        return sqlTypeForString;
    }

    /**
     * 覆盖默认的用于处理prepare statement 中的字符串占位符时所使用的SQL数据类型：java.sql.Types.VARCHAR
     *
     * @param sqlTypeForString 处理prepare statement 中的字符串占位符时所使用的SQL数据类型
     */
    public void setSqlTypeForString(int sqlTypeForString) {
        this.sqlTypeForString = sqlTypeForString;
    }

    public int getSqlTypeForDate() {
        return sqlTypeForDate;
    }

    /**
     * 覆盖缺省的日期类型字段对应的SQL数据类型：java.sql.Types.TIMESTAMP
     *
     * @param sqlTypeForDate 日期类型字段对应的SQL数据类型
     */
    public void setSqlTypeForDate(int sqlTypeForDate) {
        this.sqlTypeForDate = sqlTypeForDate;
    }

    /**
     * 获得单一行中的数列（即单行多列）(在有数据的情况下假定只有一行,本方法支持多列,多个表的级联
     * ,和复杂的where语句).
     */
    public String[] querySingleRowItems(String fieldStr, String tableStr, String whereStr) throws Exception {
        if (CommonUtils.isStringEmpty(fieldStr))
            throw new IllegalArgumentException("fieldStr是空的，参数无效！");
        return querySingleRowItems(DBUtils.formatGetComboItemsSql(tableStr, fieldStr, whereStr, null));
    }

    /**
     * 获得单一行中的数列（即单行多列）(在有数据的情况下假定只有一行,本方法支持多列,多个表的级联
     * ,和复杂的where语句).
     *
     * @param sql 完整的SQL语句
     */
    public String[] querySingleRowItems(String sql) throws Exception {
        if (CommonUtils.isStringEmpty(sql))
            throw new IllegalArgumentException("sql是空的，参数无效！");
        Vector<?> vec = this.queryData(sql);
        String[] ret = null;
        if (!vec.isEmpty()) {
            //只取第一行(如果有多行)
            Vector<?> r = (Vector<?>) vec.get(0);
            ret = new String[r.size()];

            //遍历所有列
            for (int i = 0; i < r.size(); i++)
                ret[i] = (String) r.get(i);
        }
        return ret;
    }

    /**
     * 获得单行单个字段值(如获取的不是单个字段且返回值不只一行则默认取第一行第一列).
     */
    public String querySingleItem(String fieldStr, String tableStr, String whereStr) throws Exception {
        if (CommonUtils.isStringEmpty(fieldStr))
            throw new IllegalArgumentException("fieldStr是空的，参数无效！");
        return querySingleItem(DBUtils.formatGetComboItemsSql(tableStr, fieldStr, whereStr, null));
    }

    /**
     * 获得单行单个字段值(如获取的不是单个字段且返回值不只一行则默认取第一行第一列).
     *
     * @param sql 完整的SQL语句
     */
    public String querySingleItem(String sql) throws Exception {
        String[] item = querySingleRowItems(sql);
        if (item != null && item.length > 0)
            return item[0];
        else
            return null;
    }

    /**
     * 当前数据类型是否是MySQL.
     *
     * @since 20170722
     */
    public static boolean isMySQL() {
        return DB_TYPE == DBType.mySql;
    }

    /**
     * 当前数据类型是否是SQLSever.
     *
     * @since 20170722
     */
    public static boolean isSQLServer() {
        return DB_TYPE == DBType.sqlServer;
    }

    /**
     * 当前数据类型是否是Oracle.
     *
     * @since 20170722
     */
    public static boolean isOracle() {
        return DB_TYPE == DBType.oracle;
    }

    /**
     * 描述数据库类型的枚举类型.
     *
     * @author js, 2011-02-16
     * @version 1.0
     */
    public enum DBType {
        oracle,
        sqlServer,
        db2,
        mySql,
        sybase,
        sqlLite,
        postgreSQL,
        msAccess,
        interBase;
    }
}
